# -*- coding: utf-8 -*-
"""
    xfree.cli.utils
    ~~~~~~~~~~~~~~~~~~~~~
    文件描述：命令行工具函数
    作者：xfree
    邮箱：cy_83k@qq.com
"""
import os
import re
import sys

import click
from flask import __version__ as flask_version
from flask import current_app
from flask_themes2 import get_theme

from xfree import __version__
from xfree.utils.populate import create_user, update_user

# 定义电子邮件正则表达式
_email_regex = r"[^@]+@[^@]+\.[^@]+"


class XFreeCLIError(click.ClickException):
    """自定义的 xfree 命令行错误异常类。

    这个异常类用于在命令行中显示带有颜色支持的错误信息，一旦抛出这个异常，将停止进一步地处理。

    参数：
        message：错误消息。
        **styles：传递给 click.secho 的样式参数。
    """

    def __init__(self, message, **styles):
        # 调用父类构造函数初始化异常
        click.ClickException.__init__(self, message)
        # 保存样式参数
        self.styles = styles

    def show(self, file=None):
        """
        在标准错误流中显示错误信息，并应用样式参数。
        参数：
            file：文件对象，用于输出错误信息。如果未指定，则默认为标准错误流。
        """
        # 如果没有指定输出文件，则默认为标准错误流
        if file is None:
            # 使用 click._compat.get_text_stderr() 获取标准错误流
            file = click._compat.get_text_stderr()
        # 使用 click.secho 在标准错误流中显示错误信息，并应用样式参数
        click.secho("错误: %s" % self.format_message(), file=file, **self.styles)


class EmailType(click.ParamType):
    """电子邮件类型。

    这个类作为 click.ParamType 的子类，用于验证输入是否为有效的电子邮件格式。
    """

    name = "email"

    def convert(self, value, param, ctx):
        # 如果输入与电子邮件正则表达式匹配，则返回输入值
        if re.match(_email_regex, value):
            return value
        else:
            # 如果输入不是有效的电子邮件格式，则抛出错误
            self.fail(("无效邮件: %s" % value), param, ctx)

    def __repr__(self):
        # 返回类的名称，用于调试和错误信息显示
        return "email"


def validate_plugin(plugin):
    """验证插件是否已安装。

    参数：
        plugin：插件名称。
    """
    # 获取当前应用的插件列表
    # list_name 包含所有插件名称，包括已禁用的插件（它们被设置为 'blocked' 在 pluggy 中，不会执行任何操作）
    if plugin not in current_app.pluggy.list_name():
        # 如果插件不在列表中，则抛出错误
        raise XFreeCLIError("未找到 {} 插件。".format(plugin), fg="red")
    return True


def validate_theme(theme):
    """验证主题是否已安装。

    参数：
        theme：主题名称。
    """
    try:
        # 尝试获取给定的主题
        get_theme(theme)
    except KeyError:
        # 如果引发 KeyError 异常，则说明主题未找到，抛出错误
        raise XFreeCLIError("未找到 {} 主题。".format(theme), fg="red")


def get_cookiecutter():
    """获取 cookiecutter 模块。

    这个函数检查 cookiecutter 是否已安装，如果未安装则抛出错误。
    """
    cookiecutter_available = False
    try:
        # 尝试导入 cookiecutter 模块
        from cookiecutter.main import cookiecutter

        cookiecutter_available = True
    except ImportError:
        pass

    if not cookiecutter_available:
        # 如果 cookiecutter 未安装，则抛出错误
        raise XFreeCLIError(
            "无法继续，因为没有安装 cookiecutter，您可以使用‘pip install cookiecutter’安装它。",
            fg="red",
        )
    return cookiecutter


def get_version(ctx, param, value):
    """获取版本信息。

    这个函数作为命令行参数的回调函数，用于显示 xfree 的版本信息。

    参数：
        ctx：命令行上下文。
        param：命令行参数。
        value：命令行参数的值。
    """
    if not value or ctx.resilient_parsing:
        return
    # 构建版本信息字符串
    message = (
        "xfree %(version)s using Flask %(flask_version)s on "
        "Python %(python_version)s"
    )
    # 在标准输出中显示版本信息，并应用上下文的颜色设置
    click.echo(
        message
        % {
            "version": __version__,
            "flask_version": flask_version,
            "python_version": sys.version.split("\n")[0],
        },
        color=ctx.color,
    )
    # 退出程序
    ctx.exit()


def prompt_save_user(username, email, password, group, only_update=False):
    """提示用户输入用户信息并创建或更新用户。

    如果某个参数为空，则提示用户输入相应的值。根据 only_update 参数决定是创建新用户还是更新现有用户。

    参数：
        username：用户名。
        email：电子邮件地址。
        password：密码。
        group：用户组。
        only_update：是否仅更新用户信息。
    """
    if not username:
        # 如果用户名为空，则提示用户输入用户名
        username = click.prompt(
            click.style("用户名", fg="magenta"),
            type=str,
            default=os.environ.get("USER", ""),
        )
    if not email:
        # 如果电子邮件地址为空，则提示用户输入电子邮件地址
        email = click.prompt(
            click.style("电子邮件地址", fg="magenta"), type=EmailType()
        )
    if not password:
        # 如果密码为空，则提示用户输入密码，并要求确认密码
        password = click.prompt(
            click.style("密码", fg="magenta"),
            hide_input=True,
            confirmation_prompt=click.style("确认密码", fg="magenta"),
        )
    if not group:
        # 如果用户组为空，则提示用户选择用户组
        group = click.prompt(
            click.style("用户组", fg="magenta"),
            type=click.Choice(["admin", "super_mod", "mod", "member"]),
            default="admin",
        )

    if only_update:
        # 如果 only_update 为 True，则调用 update_user 函数更新用户信息
        return update_user(username, password, email, group)
    else:
        # 否则，调用 create_user 函数创建新用户
        return create_user(username, password, email, group)


def prompt_config_path(config_path):
    """提示用户输入配置文件路径。

    如果给定的路径已经存在，则询问用户是否覆盖。如果用户确认覆盖，则使用该路径；否则，继续提示用户输入新的路径，直到输入一个不存在的路径为止。

    参数：
        config_path：配置文件路径。
    """
    click.secho("保存此配置文件的路径。", fg="cyan")
    while True:
        if os.path.exists(config_path) and click.confirm(
            click.style(
                "配置 {cfg} 已存在。你想覆盖它吗？".format(
                    cfg=config_path
                ),
                fg="magenta",
            )
        ):
            break

        config_path = click.prompt(
            click.style("保存到", fg="magenta"), default=config_path
        )

        if not os.path.exists(config_path):
            break

    return config_path


def write_config(config, config_template, config_path):
    """写入配置文件。

    根据给定的配置字典和配置模板，将新的配置文件写入指定的路径。

    参数：
        config：配置字典，包含所有要写入配置文件的键值对。
        config_template：配置模板（jinja2 模板）。
        config_path：配置文件的写入路径。
    """
    with open(config_path, "wb") as cfg_file:
        # 将配置模板渲染后的内容写入配置文件，编码为 UTF-8
        cfg_file.write(config_template.render(**config).encode("utf-8"))
